// 滑动校验组件
Vue.component('vue-puzzle-vcode', {
    props: {
        canvasWidth: { type: Number, default: 310 }, // 主canvas的宽
        canvasHeight: { type: Number, default: 160 }, // 主canvas的高
        // 是否出现，由父级控制
        show: { type: Boolean, default: false },
        puzzleScale: { type: Number, default: 1 }, // 拼图块的大小缩放比例
        sliderSize: { type: Number, default: 50 }, // 滑块的大小
        range: { type: Number, default: 10 }, // 允许的偏差值
        // 所有的背景图片
        imgs: {
            type: Array
        },
        successText: {
            type: String,
            default: "验证通过！"
        },
        failText: {
            type: String,
            default: "验证失败，请重试"
        },
        sliderText: {
            type: String,
            default: "拖动滑块完成拼图"
        }
    },
    data() {
        return {
            mouseDown: false, // 鼠标是否在按钮上按下
            startWidth: 50, // 鼠标点下去时父级的width
            startX: 0, // 鼠标按下时的X
            newX: 0, // 鼠标当前的偏移X
            pinX: 0, // 拼图的起始X
            pinY: 0, // 拼图的起始Y
            loading: false, // 是否正在加在中，主要是等图片onload
            isCanSlide: false, // 是否可以拉动滑动条
            error: false, // 图片加在失败会出现这个，提示用户手动刷新
            infoBoxShow: false, // 提示信息是否出现
            infoText: "", // 提示等信息
            infoBoxFail: false, // 是否验证失败
            timer1: null, // setTimout1
            closeDown: false, // 为了解决Mac上的click BUG
            isSuccess: false, // 验证成功
            imgIndex: -1, // 用于自定义图片时不会随机到重复的图片
            isSubmting: false, // 是否正在判定，主要用于判定中不能点击重置按钮
            resetSvg: "el-icon-refresh",
        };
    },
    template: '<div :class="[\'vue-puzzle-vcode\', { show_: show }]"\n' +
        '       @mousedown="onCloseMouseDown"\n' +
        '       @mouseup="onCloseMouseUp"\n' +
        '       @touchstart="onCloseMouseDown"\n' +
        '       @touchend="onCloseMouseUp">\n' +
        '    <div class="vue-auth-box_"\n' +
        '         @mousedown.stop\n' +
        '         @touchstart.stop>\n' +
        '      <div class="auth-body_"\n' +
        '           :style="`height: ${canvasHeight}px`">\n' +
        '        <!-- 主图，有缺口 -->\n' +
        '        <canvas ref="canvas1"\n' +
        '                :width="canvasWidth"\n' +
        '                :height="canvasHeight"\n' +
        '                :style="`width:${canvasWidth}px;height:${canvasHeight}px`" />\n' +
        '        <!-- 成功后显示的完整图 -->\n' +
        '        <canvas ref="canvas3"\n' +
        '                :class="[\'auth-canvas3_\', { show: isSuccess }]"\n' +
        '                :width="canvasWidth"\n' +
        '                :height="canvasHeight"\n' +
        '                :style="`width:${canvasWidth}px;height:${canvasHeight}px`" />\n' +
        '        <!-- 小图 -->\n' +
        '        <canvas :width="puzzleBaseSize"\n' +
        '                class="auth-canvas2_"\n' +
        '                :height="canvasHeight"\n' +
        '                ref="canvas2"\n' +
        '                :style="\n' +
        '            `width:${puzzleBaseSize}px;height:${canvasHeight}px;transform:translateX(${styleWidth -\n' +
        '              sliderBaseSize -\n' +
        '              (puzzleBaseSize - sliderBaseSize) *\n' +
        '                ((styleWidth - sliderBaseSize) /\n' +
        '                  (canvasWidth - sliderBaseSize))}px)`\n' +
        '          " />\n' +
        '        <div :class="[\'loading-box_\', { hide_: !loading }]">\n' +
        '          <div class="loading-gif_">\n' +
        '            <span></span>\n' +
        '            <span></span>\n' +
        '            <span></span>\n' +
        '            <span></span>\n' +
        '            <span></span>\n' +
        '          </div>\n' +
        '        </div>\n' +
        '        <div :class="[\'info-box_\', { show: infoBoxShow }, { fail: infoBoxFail }]">\n' +
        '          {{ infoText }}\n' +
        '        </div>\n' +
        '        <div :class="[\'flash_\', { show: isSuccess }]"\n' +
        '             :style="\n' +
        '            `transform: translateX(${\n' +
        '              isSuccess\n' +
        '                ? `${canvasWidth + canvasHeight * 0.578}px`\n' +
        '                : `-${canvasHeight * 0.578}px`\n' +
        '            }) skew(-30deg, 0);`\n' +
        '          "></div>\n' +
        '        <i class="reset_" :class="resetSvg" @click="reset" />\n' +
        '      </div>\n' +
        '      <div class="auth-control_">\n' +
        '        <div class="range-box"\n' +
        '             :style="`height:${sliderBaseSize}px`">\n' +
        '          <div class="range-text">{{ sliderText }}</div>\n' +
        '          <div class="range-slider"\n' +
        '               ref="range-slider"\n' +
        '               :style="`width:${styleWidth}px`">\n' +
        '            <div :class="[\'range-btn\', { isDown: mouseDown }]"\n' +
        '                 :style="`width:${sliderBaseSize}px`"\n' +
        '                 @mousedown="onRangeMouseDown($event)"\n' +
        '                 @touchstart="onRangeMouseDown($event)">\n' +
        '              <div></div>\n' +
        '              <div></div>\n' +
        '              <div></div>\n' +
        '            </div>\n' +
        '          </div>\n' +
        '        </div>\n' +
        '      </div>\n' +
        '    </div>\n' +
        '  </div>',
    mounted: function () {
        document.body.appendChild(this.$el);
        document.addEventListener("mousemove", this.onRangeMouseMove, false);
        document.addEventListener("mouseup", this.onRangeMouseUp, false);

        document.addEventListener("touchmove", this.onRangeMouseMove, {
            passive: false
        });
        document.addEventListener("touchend", this.onRangeMouseUp, false);
        if (this.show) {
            document.body.classList.add("vue-puzzle-overflow");
            this.reset();
        }
    },
    beforeDestroy() {
        clearTimeout(this.timer1);
        document.body.removeChild(this.$el);
        document.removeEventListener("mousemove", this.onRangeMouseMove, false);
        document.removeEventListener("mouseup", this.onRangeMouseUp, false);

        document.removeEventListener("touchmove", this.onRangeMouseMove, {
            passive: false
        });
        document.removeEventListener("touchend", this.onRangeMouseUp, false);
    },

    /** 监听 **/
    watch: {
        show(newV) {
            // 每次出现都应该重新初始化
            if (newV) {
                document.body.classList.add("vue-puzzle-overflow");
                this.reset();
            } else {
                this.isSubmting = false;
                this.isSuccess = false;
                this.infoBoxShow = false;
                document.body.classList.remove("vue-puzzle-overflow");
            }
        }
    },
    /** 计算属性 **/
    computed: {
        // styleWidth是底部用户操作的滑块的父级，就是轨道在鼠标的作用下应该具有的宽度
        styleWidth() {
            const w = this.startWidth + this.newX - this.startX;
            return w < this.sliderBaseSize
                ? this.sliderBaseSize
                : w > this.canvasWidth
                    ? this.canvasWidth
                    : w;
        },
        // 图中拼图块的60 * 用户设定的缩放比例计算之后的值 0.2~2
        puzzleBaseSize() {
            return Math.round(
                Math.max(Math.min(this.puzzleScale, 2), 0.2) * 52.5 + 6
            );
        },
        // 处理一下sliderSize，弄成整数，以免计算有偏差
        sliderBaseSize() {
            return Math.max(
                Math.min(
                    Math.round(this.sliderSize),
                    Math.round(this.canvasWidth * 0.5)
                ),
                10
            );
        }
    },

    /** 方法 **/
    methods: {
        // 关闭
        onClose() {
            if (!this.mouseDown && !this.isSubmting) {
                clearTimeout(this.timer1);
                this.$emit("close");
            }
        },
        onCloseMouseDown() {
            this.closeDown = true;
        },
        onCloseMouseUp() {
            if (this.closeDown) {
                this.onClose();
            }
            this.closeDown = false;
        },
        // 鼠标按下准备拖动
        onRangeMouseDown(e) {
            if (this.isCanSlide) {
                this.mouseDown = true;
                this.startWidth = this.$refs["range-slider"].clientWidth;
                this.newX = e.clientX || e.changedTouches[0].clientX;
                this.startX = e.clientX || e.changedTouches[0].clientX;
            }
        },
        // 鼠标移动
        onRangeMouseMove(e) {
            if (this.mouseDown) {
                e.preventDefault();
                this.newX = e.clientX || e.changedTouches[0].clientX;
            }
        },
        // 鼠标抬起
        onRangeMouseUp() {
            if (this.mouseDown) {
                this.mouseDown = false;
                this.submit();
            }
        },
        /**
         * 开始进行
         * @param withCanvas 是否强制使用canvas随机作图
         */
        init(withCanvas) {
            // 防止重复加载导致的渲染错误
            if(this.loading && !withCanvas){
                return;
            }
            this.loading = true;
            this.isCanSlide = false;
            const c = this.$refs.canvas1;
            const c2 = this.$refs.canvas2;
            const c3 = this.$refs.canvas3;
            const ctx = c.getContext("2d");
            const ctx2 = c2.getContext("2d");
            const ctx3 = c3.getContext("2d");
            const isFirefox = navigator.userAgent.indexOf("Firefox") >= 0 && navigator.userAgent.indexOf("Windows") >= 0; // 是windows版火狐
            const img = document.createElement("img");
            ctx.fillStyle = "rgba(255,255,255,1)";
            ctx3.fillStyle = "rgba(255,255,255,1)";
            ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
            ctx2.clearRect(0, 0, this.canvasWidth, this.canvasHeight);

            // 取一个随机坐标，作为拼图块的位置
            this.pinX = this.getRandom(this.puzzleBaseSize,this.canvasWidth - this.puzzleBaseSize - 20); // 留20的边距
            this.pinY = this.getRandom(20,this.canvasHeight - this.puzzleBaseSize - 20); // 主图高度 - 拼图块自身高度 - 20边距
            img.crossOrigin = "anonymous"; // 匿名，想要获取跨域的图片
            img.onload = () => {
                const [x, y, w, h] = this.makeImgSize(img);
                ctx.save();
                // 先画小图
                this.paintBrick(ctx);
                ctx.closePath();
                if(!isFirefox){
                    ctx.shadowOffsetX = 0;
                    ctx.shadowOffsetY = 0;
                    ctx.shadowColor = "#000";
                    ctx.shadowBlur = 3;
                    ctx.fill();
                    ctx.clip();
                } else {
                    ctx.clip();
                    ctx.save();
                    ctx.shadowOffsetX = 0;
                    ctx.shadowOffsetY = 0;
                    ctx.shadowColor = "#000";
                    ctx.shadowBlur = 3;
                    ctx.fill();
                    ctx.restore();
                }

                ctx.drawImage(img, x, y, w, h);
                ctx3.fillRect(0,0,this.canvasWidth,this.canvasHeight);
                ctx3.drawImage(img, x, y, w, h);

                // 设置小图的内阴影
                ctx.globalCompositeOperation = "source-atop";

                this.paintBrick(ctx);

                ctx.arc(
                    this.pinX + Math.ceil(this.puzzleBaseSize / 2),
                    this.pinY + Math.ceil(this.puzzleBaseSize / 2),
                    this.puzzleBaseSize * 1.2,
                    0,
                    Math.PI * 2,
                    true
                );
                ctx.closePath();
                ctx.shadowColor = "rgba(255, 255, 255, .8)";
                ctx.shadowOffsetX = -1;
                ctx.shadowOffsetY = -1;
                ctx.shadowBlur = Math.min(Math.ceil(8 * this.puzzleScale), 12);
                ctx.fillStyle = "#ffffaa";
                ctx.fill();

                // 将小图赋值给ctx2
                const imgData = ctx.getImageData(
                    this.pinX - 3, // 为了阴影 是从-3px开始截取，判定的时候要+3px
                    this.pinY - 20,
                    this.pinX + this.puzzleBaseSize + 5,
                    this.pinY + this.puzzleBaseSize + 5
                );
                ctx2.putImageData(imgData, 0, this.pinY - 20);

                // ctx2.drawImage(c, this.pinX - 3,this.pinY - 20,this.pinX + this.puzzleBaseSize + 5,this.pinY + this.puzzleBaseSize + 5,
                // 0, this.pinY - 20, this.pinX + this.puzzleBaseSize + 5, this.pinY + this.puzzleBaseSize + 5);

                // 清理
                ctx.restore();
                ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);

                // 画缺口
                ctx.save();
                this.paintBrick(ctx);
                ctx.globalAlpha = 0.8;
                ctx.fillStyle = "#ffffff";
                ctx.fill();
                ctx.restore();

                // 画缺口的内阴影
                ctx.save();
                ctx.globalCompositeOperation = "source-atop";
                this.paintBrick(ctx);
                ctx.arc(
                    this.pinX + Math.ceil(this.puzzleBaseSize / 2),
                    this.pinY + Math.ceil(this.puzzleBaseSize / 2),
                    this.puzzleBaseSize * 1.2,
                    0,
                    Math.PI * 2,
                    true
                );
                ctx.shadowColor = "#000";
                ctx.shadowOffsetX = 2;
                ctx.shadowOffsetY = 2;
                ctx.shadowBlur = 16;
                ctx.fill();
                ctx.restore();

                // 画整体背景图
                ctx.save();
                ctx.globalCompositeOperation = "destination-over";
                ctx.drawImage(img, x, y, w, h);
                ctx.restore();

                this.loading = false;
                this.isCanSlide = true;
            };
            img.onerror = () => {
                this.init(true); // 如果图片加载错误就重新来，并强制用canvas随机作图
            };

            if (!withCanvas && this.imgs && this.imgs.length) {
                let randomNum = this.getRandom(0, this.imgs.length - 1);
                if (randomNum === this.imgIndex) {
                    if (randomNum === this.imgs.length - 1) {
                        randomNum = 0;
                    } else {
                        randomNum++;
                    }
                }
                this.imgIndex = randomNum;
                img.src = this.imgs[randomNum];
            } else {
                img.src = this.makeImgWithCanvas();
            }
        },
        // 工具 - 范围随机数
        getRandom(min, max) {
            return Math.ceil(Math.random() * (max - min) + min);
        },
        // 工具 - 设置图片尺寸cover方式贴合canvas尺寸 w/h
        makeImgSize(img) {
            const imgScale = img.width / img.height;
            const canvasScale = this.canvasWidth / this.canvasHeight;
            let x = 0,
                y = 0,
                w = 0,
                h = 0;
            if (imgScale > canvasScale) {
                h = this.canvasHeight;
                w = imgScale * h;
                y = 0;
                x = (this.canvasWidth - w) / 2;
            } else {
                w = this.canvasWidth;
                h = w / imgScale;
                x = 0;
                y = (this.canvasHeight - h) / 2;
            }
            return [x, y, w, h];
        },
        // 绘制拼图块的路径
        paintBrick(ctx) {
            const moveL = Math.ceil(15 * this.puzzleScale); // 直线移动的基础距离
            ctx.beginPath();
            ctx.moveTo(this.pinX, this.pinY);
            ctx.lineTo(this.pinX + moveL, this.pinY);
            ctx.arcTo(
                this.pinX + moveL,
                this.pinY - moveL / 2,
                this.pinX + moveL + moveL / 2,
                this.pinY - moveL / 2,
                moveL / 2
            );
            ctx.arcTo(
                this.pinX + moveL + moveL,
                this.pinY - moveL / 2,
                this.pinX + moveL + moveL,
                this.pinY,
                moveL / 2
            );
            ctx.lineTo(this.pinX + moveL + moveL + moveL, this.pinY);
            ctx.lineTo(this.pinX + moveL + moveL + moveL, this.pinY + moveL);
            ctx.arcTo(
                this.pinX + moveL + moveL + moveL + moveL / 2,
                this.pinY + moveL,
                this.pinX + moveL + moveL + moveL + moveL / 2,
                this.pinY + moveL + moveL / 2,
                moveL / 2
            );
            ctx.arcTo(
                this.pinX + moveL + moveL + moveL + moveL / 2,
                this.pinY + moveL + moveL,
                this.pinX + moveL + moveL + moveL,
                this.pinY + moveL + moveL,
                moveL / 2
            );
            ctx.lineTo(
                this.pinX + moveL + moveL + moveL,
                this.pinY + moveL + moveL + moveL
            );
            ctx.lineTo(this.pinX, this.pinY + moveL + moveL + moveL);
            ctx.lineTo(this.pinX, this.pinY + moveL + moveL);

            ctx.arcTo(
                this.pinX + moveL / 2,
                this.pinY + moveL + moveL,
                this.pinX + moveL / 2,
                this.pinY + moveL + moveL / 2,
                moveL / 2
            );
            ctx.arcTo(
                this.pinX + moveL / 2,
                this.pinY + moveL,
                this.pinX,
                this.pinY + moveL,
                moveL / 2
            );
            ctx.lineTo(this.pinX, this.pinY);
        },
        // 用canvas随机生成图片
        makeImgWithCanvas() {
            const canvas = document.createElement("canvas");
            const ctx = canvas.getContext("2d");
            canvas.width = this.canvasWidth;
            canvas.height = this.canvasHeight;
            ctx.fillStyle = `rgb(${this.getRandom(100, 255)},${this.getRandom(
                100,
                255
            )},${this.getRandom(100, 255)})`;
            ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
            // 随机画10个图形
            for (let i = 0; i < 12; i++) {
                ctx.fillStyle = `rgb(${this.getRandom(100, 255)},${this.getRandom(
                    100,
                    255
                )},${this.getRandom(100, 255)})`;
                ctx.strokeStyle = `rgb(${this.getRandom(100, 255)},${this.getRandom(
                    100,
                    255
                )},${this.getRandom(100, 255)})`;

                if (this.getRandom(0, 2) > 1) {
                    // 矩形
                    ctx.save();
                    ctx.rotate((this.getRandom(-90, 90) * Math.PI) / 180);
                    ctx.fillRect(
                        this.getRandom(-20, canvas.width - 20),
                        this.getRandom(-20, canvas.height - 20),
                        this.getRandom(10, canvas.width / 2 + 10),
                        this.getRandom(10, canvas.height / 2 + 10)
                    );
                    ctx.restore();
                } else {
                    // 圆
                    ctx.beginPath();
                    const ran = this.getRandom(-Math.PI, Math.PI);
                    ctx.arc(
                        this.getRandom(0, canvas.width),
                        this.getRandom(0, canvas.height),
                        this.getRandom(10, canvas.height / 2 + 10),
                        ran,
                        ran + Math.PI * 1.5
                    );
                    ctx.closePath();
                    ctx.fill();
                }
            }
            return canvas.toDataURL("image/png");
        },
        // 开始判定
        submit() {
            this.isSubmting = true;
            // 偏差 x = puzzle的起始X - (用户真滑动的距离) + (puzzle的宽度 - 滑块的宽度) * （用户真滑动的距离/canvas总宽度）
            // 最后+ 的是补上slider和滑块宽度不一致造成的缝隙
            const x = Math.abs(
                this.pinX -
                (this.styleWidth - this.sliderBaseSize) +
                (this.puzzleBaseSize - this.sliderBaseSize) *
                ((this.styleWidth - this.sliderBaseSize) /
                    (this.canvasWidth - this.sliderBaseSize)) -
                3
            );
            if (x < this.range) {
                // 成功
                this.infoText = this.successText;
                this.infoBoxFail = false;
                this.infoBoxShow = true;
                this.isCanSlide = false;
                this.isSuccess = true;
                // 成功后准备关闭
                clearTimeout(this.timer1);
                this.timer1 = setTimeout(() => {
                    // 成功的回调
                    this.isSubmting = false;
                    this.$emit("success", x);
                }, 800);
            } else {
                // 失败
                this.infoText = this.failText;
                this.infoBoxFail = true;
                this.infoBoxShow = true;
                this.isCanSlide = false;
                // 失败的回调
                this.$emit("fail", x);
                // 800ms后重置
                clearTimeout(this.timer1);
                this.timer1 = setTimeout(() => {
                    this.isSubmting = false;
                    this.reset();
                }, 800);
            }
        },
        // 重置 - 重新设置初始状态
        resetState() {
            this.infoBoxFail = false;
            this.infoBoxShow = false;
            this.isCanSlide = false;
            this.isSuccess = false;
            this.startWidth = this.sliderBaseSize; // 鼠标点下去时父级的width
            this.startX = 0; // 鼠标按下时的X
            this.newX = 0; // 鼠标当前的偏移X
        },

        // 重置
        reset() {
            if(this.isSubmting){
                return;
            }
            this.resetState();
            this.init();
        }
    }

});